/********************************************************************************************/
/* Fullhan Technology (Shanghai) Co., Ltd.                                                  */
/* Fullhan Proprietary and Confidential                                                     */
/* Copyright (c) 2017 Fullhan Technology Group Limited and its affiliated companies         */
/********************************************************************************************/
#include "hw_cesa_if.h"
#include <linux/interrupt.h>
#include <linux/fh_dmac.h>
#include <mach/fh_cesa_plat.h>

#define EncryptControl		(0x0)
#define ClkGatingBypass		(0x4)
#define FIFOStatus			(0x8)
#define PErrStatus			(0xC)
#define ClearKey0			(0x10)
#define ClearKey1			(0x14)
#define ClearKey2			(0x18)
#define ClearKey3			(0x1C)
#define ClearKey4			(0x20)
#define ClearKey5			(0x24)
#define ClearKey6			(0x28)
#define ClearKey7			(0x2C)
#define InitIV0				(0x30)
#define InitIV1				(0x34)
#define InitIV2				(0x38)
#define InitIV3				(0x3C)
#ifdef CONFIG_ARCH_FH889X_FH8210
#define DMASrc0				(0x40)
#define DMASrc1				(0x44)
#define DMADes0				(0x48)
#define DMADes1				(0x4C)
#else
#define DMASrc0				(0x48)
#define DMADes0				(0x4C)
#endif
#define DMATrasSize			(0x50)
#define DMACtrl				(0x54)
#define FIFOThold			(0x58)
#define IntrEnable			(0x5C)
#define IntrSrc				(0x60)
#define MaskedIntrStatus	(0x64)
#define IntrClear			(0x68)
#define ExtDmaDebug0		(0x6c)
#define ExtDmaDebug1		(0x70)
#define ExtDmaDebug2		(0x74)
#define ExtDmaDebug3		(0x78)
#define ExtDmaDebug4		(0x7C)
#define LastIV0				(0x80)
#define LastIV1				(0x84)
#define LastIV2				(0x88)
#define LastIV3				(0x8C)
#define ExtDmaCfg0			(0x90)
#define ExtDmaCfg1			(0x94)
#define ExtDmaCfg2			(0x98)

typedef union _HW_CIPHER_CTRL_REG
{
	uint32_t all;
	struct {
	uint32_t OPER_MODE:1;
	uint32_t ALGO_MODE:3;
	uint32_t WORK_MODE:3;
	uint32_t FIRST_BLOCK:1;
	uint32_t DES_PCHK:1;
	uint32_t SELF_KEY_GEN_EN:1;
	uint32_t SELF_SEC_FIRST:1;
	uint32_t SELF_SEC_IV_SEL:1;
	uint32_t SELF_KEY_ENDIAN:1;
	uint32_t :19;
	} bitc;
} HW_CIPHER_CTRL_REG;

static void biglittle_swap(uint8_t *buf)
{
	uint8_t tmp, tmp1;

	tmp = buf[0];
	tmp1 = buf[1];
	buf[0] = buf[3];
	buf[1] = buf[2];
	buf[2] = tmp1;
	buf[3] = tmp;
}

extern bool ades_use_efuse_key(u32 key_size,
								u32 mode,
								u32 map_size,
								u32 map[][2],
								u32 is_enc);
int32_t hw_cesa_ades_config(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	HW_CIPHER_CTRL_REG ctrl_reg;
	uint32_t temp_key_buf[8];
	uint32_t temp_iv_buf[4];
	uint32_t i, key_size, iv_size;

	ctrl_reg.all = ADES_REG32(EncryptControl);
	ctrl_reg.bitc.OPER_MODE = hw_ctrl->oper_mode;
	ctrl_reg.bitc.ALGO_MODE = hw_ctrl->algo_mode;
	ctrl_reg.bitc.WORK_MODE = hw_ctrl->work_mode;
	ctrl_reg.bitc.FIRST_BLOCK = 1;
	ctrl_reg.bitc.SELF_KEY_GEN_EN = hw_ctrl->self_key_gen;
	ADES_REG32(EncryptControl) = ctrl_reg.all;

	//================= KEY =================
	switch (hw_ctrl->algo_mode)
	{
	case HW_CESA_ADES_ALGO_MODE_DES:
		key_size = 8;
		break;
	case HW_CESA_ADES_ALGO_MODE_TDES:
		key_size = 24;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES128:
		key_size = 16;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES192:
		key_size = 24;
		break;
	case HW_CESA_ADES_ALGO_MODE_AES256:
		key_size = 32;
		break;
	default:
		pr_err("cipher error algo_mode!!\n");
		return CESA_ADES_ALGO_MODE_ERROR;
	}

	if (ades_use_efuse_key(key_size,
		hw_ctrl->efuse_para.mode,
		hw_ctrl->efuse_para.ex_key_para.map_size,
		(uint32_t (*)[2])hw_ctrl->efuse_para.ex_key_para.map,
		(hw_ctrl->oper_mode == HW_CESA_ADES_OPER_MODE_ENC))
		== false) {
		memcpy((uint8_t *) &temp_key_buf[0], hw_ctrl->key, key_size);
		for (i = 0; i < key_size / sizeof(uint32_t); i++) {
	    	biglittle_swap((uint8_t *) (temp_key_buf + i));
			ADES_REG32(ClearKey0 + 4*i) = temp_key_buf[i];
		}
	}

	//================= IV =================
	switch (hw_ctrl->work_mode)
	{
	case HW_CESA_ADES_WORK_MODE_CBC:
	case HW_CESA_ADES_WORK_MODE_CTR:
	case HW_CESA_ADES_WORK_MODE_CFB:
	case HW_CESA_ADES_WORK_MODE_OFB:
		if (hw_ctrl->algo_mode >= HW_CESA_ADES_ALGO_MODE_AES128)
			iv_size = 16;
		else
			iv_size = 8;
		break;
	case HW_CESA_ADES_WORK_MODE_ECB:
		iv_size = 0;
		break;
	default:
		pr_err("cipher error work_mode!!\n");
		return CESA_ADES_WORK_MODE_ERROR;
	}


	for (i = 0; i < iv_size; i++) {
		if (hw_ctrl->iv_last[i] != 0) {
			uint32_t *temp_iv_ptr = (uint32_t *)hw_ctrl->iv_last;
			for (i = 0; i < iv_size / sizeof(uint32_t); i++)
				ADES_REG32(InitIV0 + 4*i) = temp_iv_ptr[i];

			break;
		}
	}

	if (i >= iv_size) {
		memcpy((uint8_t *) &temp_iv_buf[0], hw_ctrl->iv_init, iv_size);
		for (i = 0; i < iv_size / sizeof(uint32_t); i++) {
			biglittle_swap((uint8_t *) (temp_iv_buf + i));
			ADES_REG32(InitIV0 + 4*i) = temp_iv_buf[i];
		}
	}

	//================= algo_mode & work_mode conflict check =================
	if (hw_ctrl->algo_mode <= HW_CESA_ADES_ALGO_MODE_TDES
		&& hw_ctrl->work_mode == HW_CESA_ADES_WORK_MODE_CTR)
		return CESA_ADES_WORK_ALGO_MODE_CONFLICT;

	return CESA_SUCCESS;
}

void hw_ades_config_selfkey(HW_CESA_ADES_CTRL_S *hw_ctrl, bool use_self_key, uint8_t *self_key_text)
{
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	if (plat_data->ext_dma_enable != CESA_USE_EXT_DMA)
		return;

	if (use_self_key) {
		hw_ctrl->self_key_gen = true;
		memcpy(hw_ctrl->ker_selfkey_addr, self_key_text, KEY_MAX_SIZE);
	} else {
		hw_ctrl->self_key_gen = false;
		memset(hw_ctrl->ker_selfkey_addr, 0, KEY_MAX_SIZE);
	}

	return;
}

int32_t hw_ades_ext_dma_req_chan(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	dma_cap_mask_t mask;
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	if (plat_data->ext_dma_enable != CESA_USE_EXT_DMA)
		return CESA_SUCCESS;

	dma_cap_zero(mask);
	dma_cap_set(DMA_SLAVE, mask);
	hw_ctrl->src_chan = dma_request_channel(mask, 0, 0);

	if (hw_ctrl->src_chan == NULL)
		return CESA_INVALID_VALUE;

	hw_ctrl->dst_chan = dma_request_channel(mask, 0, 0);
	if (hw_ctrl->dst_chan == NULL) {
		dma_release_channel(hw_ctrl->src_chan);
		return CESA_INVALID_VALUE;
	}

	hw_ctrl->ker_selfkey_addr = dma_alloc_coherent(CESA_DEV, KEY_MAX_SIZE,
			&hw_ctrl->phy_selfkey_addr, GFP_KERNEL);
	if (!hw_ctrl->ker_selfkey_addr) {
		dma_release_channel(hw_ctrl->src_chan);
		dma_release_channel(hw_ctrl->dst_chan);
		pr_err("failed to allocate selfkey memory!\n");
		return CESA_INVALID_VALUE;
	}

	init_completion(&hw_ctrl->src_done);
	init_completion(&hw_ctrl->dst_done);

	return CESA_SUCCESS;
}

void hw_ades_ext_dma_rel_chan(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	if (plat_data->ext_dma_enable != CESA_USE_EXT_DMA)
		return;

	dma_release_channel(hw_ctrl->src_chan);
	dma_release_channel(hw_ctrl->dst_chan);

	dma_free_coherent(CESA_DEV, KEY_MAX_SIZE,
		hw_ctrl->ker_selfkey_addr, hw_ctrl->phy_selfkey_addr);
}

int32_t hw_ades_ext_dma_done(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	uint32_t timeout = 200;
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	if (plat_data->ext_dma_enable != CESA_USE_EXT_DMA)
		return CESA_SUCCESS;

	if (wait_for_completion_timeout(&hw_ctrl->src_done, timeout) == 0) {
		pr_err("ades external dma_src timeout!\n");
		return CESA_ADES_DMA_DONE_TIMEOUT;
	}

	if (wait_for_completion_timeout(&hw_ctrl->dst_done, timeout) == 0) {
		pr_err("ades external dma_dst timeout!\n");
		return CESA_ADES_DMA_DONE_TIMEOUT;
	}

	return CESA_SUCCESS;
}

static void hw_ades_dma_src_done(void *arg)
{
	HW_CESA_ADES_CTRL_S *hw_ctrl = (HW_CESA_ADES_CTRL_S *) arg;

	complete(&hw_ctrl->src_done);
}

static void hw_ades_dma_dst_done(void *arg)
{
	HW_CESA_ADES_CTRL_S *hw_ctrl = (HW_CESA_ADES_CTRL_S *) arg;

	complete(&hw_ctrl->dst_done);
}

static void hw_ades_dma_set_src(HW_CESA_ADES_CTRL_S *hw_ctrl, uint32_t phy_src_addr, uint32_t length)
{
	struct dma_slave_config cfg = {0};
	struct fh_dma_extra ext_para = {0};
	struct scatterlist sg[2] = {0};
	struct dma_async_tx_descriptor *p_desc = 0;
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	cfg.src_addr = phy_src_addr;
	cfg.dst_addr = plat_data->phy_reg_base + ExtDmaCfg2;
	cfg.slave_id = plat_data->ext_dma_rx_handshake;
	cfg.src_maxburst = 8;
	cfg.dst_maxburst = 8;
	cfg.device_fc = false;
	cfg.direction = DMA_MEM_TO_DEV;
	dmaengine_slave_config(hw_ctrl->src_chan, &cfg);

	ext_para.sinc = FH_DMA_SLAVE_INC;
	ext_para.dinc = FH_DMA_SLAVE_FIX;
	ext_para.ot_len_flag = USR_DEFINE_ONE_TIME_LEN;
	ext_para.data_switch = SWT_ABCD_DCBA;
	ext_para.ot_len_len = 0x20;

	if (hw_ctrl->self_key_gen) {
		sg[0].dma_address = hw_ctrl->phy_selfkey_addr;
		sg[0].length = KEY_MAX_SIZE;
		sg[1].dma_address = phy_src_addr;
		sg[1].length = length;		
	} else {
		sg[0].dma_address = phy_src_addr;
		sg[0].length = length;
	}	

	p_desc = hw_ctrl->src_chan->device->device_prep_slave_sg(
		hw_ctrl->src_chan,
		sg,
		hw_ctrl->self_key_gen ? 2 : 1,
		DMA_MEM_TO_DEV,
		DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
		&ext_para);

	reinit_completion(&hw_ctrl->src_done);
	p_desc->callback = hw_ades_dma_src_done;
	p_desc->callback_param = hw_ctrl;
	p_desc->tx_submit(p_desc);
}

static void hw_ades_dma_set_dst(HW_CESA_ADES_CTRL_S *hw_ctrl, uint32_t phy_dst_addr, uint32_t length)
{
	struct dma_slave_config cfg = {0};
	struct fh_dma_extra ext_para = {0};
	struct scatterlist sg = {0};
	struct dma_async_tx_descriptor *p_desc = 0;
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	cfg.src_addr = plat_data->phy_reg_base + ExtDmaCfg2;
	cfg.dst_addr = phy_dst_addr;
	cfg.slave_id = plat_data->ext_dma_tx_handshake;
	cfg.src_maxburst = 8;
	cfg.dst_maxburst = 8;
	cfg.device_fc = false;
	cfg.direction = DMA_DEV_TO_MEM;
	dmaengine_slave_config(hw_ctrl->dst_chan, &cfg);

	ext_para.dinc = FH_DMA_SLAVE_INC;
	ext_para.sinc = FH_DMA_SLAVE_FIX;
	ext_para.ot_len_flag = USR_DEFINE_ONE_TIME_LEN;
	ext_para.data_switch = SWT_ABCD_DCBA;
	ext_para.ot_len_len = 0x20;

	sg.dma_address = phy_dst_addr;
	sg.length = length;

	p_desc = hw_ctrl->dst_chan->device->device_prep_slave_sg(
		hw_ctrl->dst_chan,
		&sg,
		1,
		DMA_DEV_TO_MEM,
		DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
		&ext_para);
	reinit_completion(&hw_ctrl->dst_done);
	p_desc->callback = hw_ades_dma_dst_done;
	p_desc->callback_param = hw_ctrl;
	p_desc->tx_submit(p_desc);
}

int32_t hw_cesa_ades_process(HW_CESA_ADES_CTRL_S *hw_ctrl, uint32_t phy_src_addr, uint32_t phy_dst_addr, uint32_t length)
{
	uint32_t outfifo_thold;
	uint32_t infifo_thold;
	struct fh_cesa_platform_data *plat_data
		= (struct fh_cesa_platform_data *)dev_get_platdata(CESA_DEV);

	if ((phy_src_addr & 0x00000003) || (phy_dst_addr & 0x00000003)) {
		pr_err("ades at dma mode: input or output address is not 4bytes multiple\n");
		return CESA_ADES_DMA_ADDR_ERROR;
	}

	//length check
	/* AES/DES CFB data length 1 byte align is permitted */
	if (hw_ctrl->algo_mode >= HW_CESA_ADES_ALGO_MODE_AES128) {
		if (length & 0xF) {
			pr_err("error: AES ECB/CBC/CFB/OFB/CTR data length should be 16 byte align!\n");
			return CESA_ADES_DATA_LENGTH_ERROR;
		}
	} else {
		if (length & 0x7) {
			pr_err("error: DES ECB/CBC/CFB/OFB data length should be 8 byte align!\n");
			return CESA_ADES_DATA_LENGTH_ERROR;
		}
	}

	if (plat_data->ext_dma_enable == CESA_USE_EXT_DMA) {
		hw_ades_dma_set_src(hw_ctrl, phy_src_addr, length);
		hw_ades_dma_set_dst(hw_ctrl, phy_dst_addr, length);

		outfifo_thold = 0x20;
		infifo_thold = 0x20;
		ADES_REG32(ExtDmaCfg1) = outfifo_thold << 16 | infifo_thold;
		ADES_REG32(IntrEnable) = 1;
		ADES_REG32(ExtDmaCfg0) = 1;

		if (hw_ctrl->self_key_gen) {
			hw_ctrl->self_key_gen = false;
			ADES_REG32(DMATrasSize) = length + 0x20;
			ADES_REG32(DMACtrl) = 0x11;
		} else {
			ADES_REG32(DMATrasSize) = length;
			ADES_REG32(DMACtrl) = 1;
		}
	} else {
		outfifo_thold = 0x40;
		infifo_thold = 0x40;
		ADES_REG32(DMASrc0) = phy_src_addr;
		ADES_REG32(DMADes0) = phy_dst_addr;
		ADES_REG32(DMATrasSize) = length;
		ADES_REG32(FIFOThold) = (outfifo_thold << 8) | infifo_thold;
		ADES_REG32(IntrEnable) = 1;
		ADES_REG32(DMACtrl) = 1;
	}

	return CESA_SUCCESS;
}

uint32_t hw_cesa_ades_intr_src(void)
{
	uint32_t intr_src;

	intr_src = ADES_REG32(IntrSrc);
	ADES_REG32(IntrClear) = 1;

	return intr_src;
}

void hw_cesa_ades_save_lastiv(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	uint32_t temp_iv_buf[4];

	temp_iv_buf[0] = ADES_REG32(LastIV0);
	temp_iv_buf[1] = ADES_REG32(LastIV1);
	temp_iv_buf[2] = ADES_REG32(LastIV2);
	temp_iv_buf[3] = ADES_REG32(LastIV3);
	memcpy(hw_ctrl->iv_last, temp_iv_buf, 16);
}

void hw_cesa_ades_restore_lastiv(HW_CESA_ADES_CTRL_S *hw_ctrl)
{
	memset(hw_ctrl->iv_last, 0, IV_MAX_SIZE);
}

